<!DOCTYPE html>
<meta charset="utf-8">
<head>
  <link rel="stylesheet" type="text/css" href="dependency.css">
</head>
<body>
  <script src="Scripts/d3.v3.js"></script>
  <script src="origin.js"></script>
  
  <!-- ================================================= -->
  <!-- ===========ACTUAL HTML           ================ -->
  <!-- ================================================= -->

  <form id="form">
    <label><input type="range" name="circle_size" min="1" max="50" value="15"/> Circle size</label><br>
    <label><input type="range" name="charge_multiplier" min="1" max="500" value="100"/> Charge multiplier</label><br>
    <label><input type="range" name="link_strength" min="0.1" max="100" value="7"/> Link strength</label><br>
    <label><input type="checkbox" name="show_texts_near_circles" checked="checked"/> Show names</label><br>
    <input id="search_input" placeholder="Type regexp to filter nodes" style="width:100%;"><br>

  </form>
  <div id="chart">
    <!-- Here the SVG will be placed-->
  </div>

<script src="Scripts/parse.js"></script>
<script>

//  ===================================================
//  =============== CONFIGURABLE PARAMS  ==============
//  ===================================================

var default_link_distance = 10;   

// How far can we change default_link_distance?
// 0   - I don't care
// 0.5 - Change it as you want, but it's preferrable to have default_link_distance 
// 1   - One does not change default_link_distance
var default_link_strength = 0.7;

// Should I comment this?
var default_circle_radius = 15;

// you can set it to true, but this will not help to understanf what's going on
var show_texts_near_circles = true;
var default_max_texts_length = 100;

// Should we use regexp-based  grouping or not
var use_regexp_color_grouping_matchers = false;

var charge_multiplier = 200;

// Each item thet matches specified regexps will be placed to correspondent group with unique color
var regexp_color_matchers = [
  "^NI",  // Nimbus
  "^UI",  // UIKit
  "^NS",  // Foundation
  "^CA",  // Core animations
];

var dependecy_graph = objcdv.parse_dependencies_graph(dependencies, use_regexp_color_grouping_matchers ? regexp_color_matchers : null);

var w = window,
    d = document,
    e = d.documentElement,
    g = d.getElementsByTagName('body')[0],
    x = w.innerWidth || e.clientWidth || g.clientWidth,
    y = w.innerHeight|| e.clientHeight|| g.clientHeight;

//  ===================================================
//  =============== http://d3js.org/ Magic ===========
//  ===================================================

// https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors
var color = d3.scale.category10();
var selectedIdx = -1
var selectedType = "normal"
var selectedobject = {}

var container = d3.select("#chart").append("svg")
      .attr("width", x)
      .attr("height", y)

//  ===================================================
//  =============== ZOOM LOGIC ========================
//  ===================================================

container.append("rect")
  .attr("width", x)
  .attr("height", y)
  .style("fill", "none")
  .style("pointer-events", "all")
  .call(d3.behavior.zoom().on("zoom", redraw))

  function redraw() {
    svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
  }    

//  ===================================================
//  =============== FORCE LAYOUT ======================
//  ===================================================

var force = d3.layout.force()
    .charge(function(d) { return d.filtered ? 0 : -d.weight * charge_multiplier})
    .linkDistance(function(l) { return l.source.filtered || l.target.filtered  ? 500 : radius(l.source) + radius(l.target) + default_link_distance})
    .size([x, y])
    .nodes(d3.values(dependecy_graph.nodes))
    .links(dependecy_graph.links)
    .linkStrength(function(l) { return l.source.filtered || l.target.filtered ? 0 : default_link_strength})
    .start();

var svg = container.append('g')

//  ===================================================
//  ===============  MARKERS SETUP   ==================
//  ===================================================

svg.append("defs").selectAll("marker")
  .data(["default", "dependency", "dependants"])
  .enter().append("marker")
    .attr("id", function(d) { return d; })
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 10)
    .attr("refY", 0)
    .attr("markerWidth", 10)
    .attr("markerHeight", 10)
    .attr("orient", "auto")
    .attr("class", "marker")
  .append("path")
    .attr("d", "M0,-5L10,0L0,5");


//  ===================================================
//  ===============  LINKS SETUP     ==================
//  ===================================================

var link = svg.append("g").selectAll("path")
    .data(dependecy_graph.links)
    .enter().append("path")
    .attr("class", "link")
    .attr("marker-end", "url(#default)")
    .style("stroke-width", function(d) { return Math.sqrt(1); })

//  ===================================================
//  ===============  NODES SETUP     ==================
//  ===================================================

var node = svg.append("g").selectAll("circle.node")
    .data(dependecy_graph.nodes)
    .enter().append("circle")
    .attr("r", radius)
    .style("fill", function(d) { return color(d.group) })
    .attr("class", "node")
    .attr("source", function(d) { return d.source})
    .attr("dest", function(d) { return d.dest})
    .call(force.drag)
    .on("click", select_node)
    .on("contextmenu", select_recursively_node)

//  ===================================================
//  ===============  TEXT NODES SETUP     =============
//  ===================================================

var text = svg.append("g").selectAll("text")
  .data(force.nodes())
  .enter().append("text")
  .attr("visibility", "visible")
  .text(function(d) { return d.name.substring(0, default_max_texts_length) });

//  ===================================================
//  ===============  FORCE UPDATE        =============
//  ===================================================
 
force.on("tick", function() {
  svg.selectAll(".node").attr("r", radius);
  link.attr("d", link_line);
  node.attr("transform", transform );
  if (show_texts_near_circles) {
    text.attr("transform", transform);
  }
});

//  ===================================================
//  ===============  HELPER FUNCTIONS     =============
//  ===================================================
function link_line(d) {
  var dx = d.target.x - d.source.x,
     dy = d.target.y - d.source.y,
     dr = Math.sqrt(dx * dx + dy * dy);

  var rsource = radius(d.sourceNode)/dr;
  var rdest = radius(d.targetNode)/dr;
  var startX = d.source.x + dx * rsource;      
  var startY = d.source.y + dy * rsource;      

  var endX = d.target.x - dx * rdest;      
  var endY = d.target.y - dy * rdest;      
  return "M" + startX+ "," + startY + "L" + endX+ "," + endY;
}

function transform(d) {
  return "translate(" + d.x + "," + d.y + ")";
}

function radius(d) {
  return default_circle_radius + default_circle_radius * d.source / 10;
}
 
/*
Window resize update
*/ 
w.onresize = function(){
   x = w.innerWidth || e.clientWidth || g.clientWidth ;
   y = w.innerHeight|| e.clientHeight|| g.clientHeight;

   container.attr("width", x ).attr("height", y);
   force.size([x, y]).start();
};


//  ===================================================
//  ===============  SELECTING_NODE       =============
//  ===================================================

function deselect_node(d) { 
  delete d.fixed
  selectedIdx = -1
  selectedobject = {};
  svg.selectAll('circle, path, text')
    .classed('filtered', false)
    .each(function(node) {
     node.filtered = false
    })
    .transition()

  svg.selectAll('.link')
    .attr("marker-end", "url(#default)")      
    .classed('filtered', false)
    .transition()


  force.start();
  return
}

function select_node(d) { 
  if (d3.event.defaultPrevented) return

  // Deselect if needed
  if (d.idx == selectedIdx && selectedType == "normal") { deselect_node(d); return }

  // Update selected object
  delete selectedobject.fixed
  selectedIdx = d.idx
  selectedobject = d
  selectedobject.fixed = true
  selectedType = "normal"

  // Figure out the neighboring node id's with brute strength because the graph is small
  var nodeNeighbors = 
  dependecy_graph.links
    .filter(function(link) {
        return link.source.index === d.index || link.target.index === d.index;})
    .map(function(link) {
        return link.source.index === d.index ? link.target.index : link.source.index; 
      }
    );

  // Fade out all circles
  svg.selectAll('circle')
  .classed('filtered', true)
  .each(function(node){
    node.filtered = true;
    node.neighbours = false;
  }).transition()
    

  svg.selectAll('text')
    .classed('filtered', true)
    .transition()
  

  svg.selectAll('.link').
    transition()
    .attr("marker-end", "")


  // Higlight all circle and texts
  svg.selectAll('circle, text')
    .filter(function(node) {
        return nodeNeighbors.indexOf(node.index) > -1 || node.index == d.index;
    })
    .classed('filtered', false)
    .each(function(node) {
       node.filtered = false;
       node.neighbours = true;
    })
    .transition()

  // Higlight links
  svg.selectAll('.link')
    .filter(function(link) {
      return link.source.index === d.index || link.target.index == d.index
    })
    .classed('filtered', false)
    .attr("marker-end", function(l) { return l.source.index === d.index ? "url(#dependency)" : "url(#dependants)"})
    .transition()

  force.start();
}

function select_recursively_node(d) { 
  if (d3.event.defaultPrevented) return
  
  // Don't show context menu :)
  d3.event.preventDefault()  

  // Deselect if needed
  if (d.idx == selectedIdx && selectedType == "recursive") { deselect_node(d); return }

  // Update selected object
  delete selectedobject.fixed
  selectedIdx = d.idx
  selectedobject = d
  selectedobject.fixed = true
  selectedType = "recursive"

  // Figure out the neighboring node id's with brute strength because the graph is small
  var neighbours = {}
  var nodeNeighbors = 
  dependecy_graph.links
    .filter(function(link) {
        return link.source.index === d.index})
    .map(function(link) {
        var idx = link.source.index === d.index ? link.target.index : link.source.index;
        if (link.source.index === d.index) {
          console.log("Step 0. Adding ",dependecy_graph.nodes[idx].name)
          neighbours[idx] = 1;
        }
        return idx; 
      }
    );

  // Next part - neighbours of neigbours
  var currentsize = Object.keys(neighbours).length
  var nextSize = 0;
  var step = 1;
  while (nextSize != currentsize) {
    console.log("Current size " + currentsize + " Next size is " + nextSize)
    currentsize = nextSize
    dependecy_graph.links
        .filter(function(link) {
            return neighbours[link.source.index] != undefined})
        .map(function(link) {
            var idx = link.target.index;
            console.log("Step "+step+". Adding ",dependecy_graph.nodes[idx].name + " From " + dependecy_graph.nodes[link.source.index].name )

            neighbours[idx] = 1;
            return idx;
          }
        );
     nextSize = Object.keys(neighbours).length   
     step = step + 1
  }

  neighbours[d.index] = 1
  nodeNeighbors = Object.keys(neighbours).map(function(neibour) {
      return parseInt(neibour);
  })


  // Fade out all circles
  svg.selectAll('circle')
  .classed('filtered', true)
  .each(function(node){
    node.filtered = true;
    node.neighbours = false;
  }).transition()
    

  svg.selectAll('text')
    .classed('filtered', true)
    .transition()
  

  svg.selectAll('.link').
    transition()
    .attr("marker-end", "")


  // Higlight all circle and texts
  svg.selectAll('circle, text')
    .filter(function(node) {
        return nodeNeighbors.indexOf(node.index) > -1 || node.index == d.index;
    })
    .classed('filtered', false)
    .each(function(node) {
       node.filtered = false;
       node.neighbours = true;
    })
    .transition()

  // Higlight links
  svg.selectAll('.link')
    .filter(function(link) {
      return nodeNeighbors.indexOf(link.source.index) > -1
    })
    .classed('filtered', false)
    .attr("marker-end", function(l) { return l.source.index === d.index ? "url(#dependency)" : "url(#dependants)"})
    .transition()

  force.start();
}

</script>

<script>
  //  ===================================================
  //  =============== INPUTS HANDLING      ==============
  //  ===================================================
  d3.selectAll("input").on("change", function change() {

    if (this.name == "circle_size") {
      default_circle_radius = parseInt(this.value);
      force.linkDistance(function(l) { return radius(l.source) + radius(l.target) + default_link_distance;})
      force.start();
    }

    if (this.name == "charge_multiplier") {
      charge_multiplier = parseInt(this.value);
      force.start();
    }

    if (this.name == "link_strength") {
      default_link_strength = parseInt(this.value) / 10;
      force.linkStrength(default_link_strength);
      force.start();
    }

    if (this.name == "show_texts_near_circles") {
      text.attr("visibility", this.checked ? "visible" : "hidden")
      show_texts_near_circles = this.checked
      force.start();
    }

  });
</script>


<script>
  //  ===================================================
  //  =============== LIVE FILTERING      ==============
  //  ===================================================
  d3.select("#search_input").on("input", function () {
    // Filter all items
    console.log("Input changed to" + this.value)
    deselect_node(selectedobject);

    if (this.value && this.value.length) {
        var re = new RegExp(this.value, "i");
        svg.selectAll('circle, text')
          .classed('filtered', function(node) {
             var filtered = !node.name.match(re);
             node.filtered = filtered;
             node.neighbours = !filtered;
             return filtered;
          })
          .transition()

       svg.selectAll('.link')
        .classed('filtered', function(l) {
             var filtered = !(l.sourceNode.name.match(re) && l.targetNode.name.match(re));
             return filtered;
        })
        .attr("marker-end", function (l) {
           var filtered = !(l.sourceNode.name.match(re) && l.targetNode.name.match(re));
           return filtered ? "" : "url(#default)"
        })
        .transition()
   
       force.start();   
    }
  });
</script>